//
// vsprintf.c
//
// Print formatting routines
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <wchar.h>
#include <sys/types.h>
#include <stdarg.h>
#include <string.h>
#include "swprintf.h"
#include <android/log.h>

//#ifdef KERNEL
//#define NOFLOAT
//#endif

#define ZEROPAD 1               // Pad with zero
#define SIGN    2               // Unsigned/signed long
#define PLUS    4               // Show plus
#define SPACE   8               // Space if plus
#define LEFT    16              // Left justified
#define SPECIAL 32              // 0x
#define LARGE   64              // Use L'ABCDEF' instead of L'abcdef'

#define is_digit(c) ((c) >= L'0' && (c) <= L'9')

static const wchar_t *digits = L"0123456789abcdefghijklmnopqrstuvwxyz";
static const wchar_t *upper_digits = L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

static size_t strnlen(const wchar_t *s, size_t count)
{
  const wchar_t *sc;
  for (sc = s; *sc != L'\0' && count--; ++sc);
  return sc - s;
}

static int skip_atoi(const wchar_t **s)
{
  int i = 0;
  while (is_digit(**s)) i = i*10 + *((*s)++) - L'0';
  return i;
}

static wchar_t *number(wchar_t *str, long num, int base, int size, int precision, int type)
{
  wchar_t c, sign, tmp[66];
  const wchar_t *dig = digits;
  int i;

  if (type & LARGE)  dig = upper_digits;
  if (type & LEFT) type &= ~ZEROPAD;
  if (base < 2 || base > 36) return 0;
  
  c = (type & ZEROPAD) ? L'0' : L' ';
  sign = 0;
  if (type & SIGN)
  {
    if (num < 0)
    {
      sign = L'-';
      num = -num;
      size--;
    }
    else if (type & PLUS)
    {
      sign = L'+';
      size--;
    }
    else if (type & SPACE)
    {
      sign = L' ';
      size--;
    }
  }

  if (type & SPECIAL)
  {
    if (base == 16)
      size -= 2;
    else if (base == 8)
      size--;
  }

  i = 0;

  if (num == 0)
    tmp[i++] = L'0';
  else
  {
    while (num != 0)
    {
      tmp[i++] = dig[((unsigned long) num) % (unsigned) base];
      num = ((unsigned long) num) / (unsigned) base;
    }
  }

  if (i > precision) precision = i;
  size -= precision;
  if (!(type & (ZEROPAD | LEFT))) while (size-- > 0) *str++ = L' ';
  if (sign) *str++ = sign;
  
  if (type & SPECIAL)
  {
    if (base == 8)
      *str++ = L'0';
    else if (base == 16)
    {
      *str++ = L'0';
      *str++ = digits[33];
    }
  }

  if (!(type & LEFT)) while (size-- > 0) *str++ = c;
  while (i < precision--) *str++ = L'0';
  while (i-- > 0) *str++ = tmp[i];
  while (size-- > 0) *str++ = L' ';

  return str;
}

static wchar_t *eaddr(wchar_t *str, unsigned wchar_t *addr, int size, int precision, int type)
{
  wchar_t tmp[24];
  const wchar_t *dig = digits;
  int i, len;

  if (type & LARGE)  dig = upper_digits;
  len = 0;
  for (i = 0; i < 6; i++)
  {
    if (i != 0) tmp[len++] = ':';
    tmp[len++] = dig[addr[i] >> 4];
    tmp[len++] = dig[addr[i] & 0x0F];
  }

  if (!(type & LEFT)) while (len < size--) *str++ = L' ';
  for (i = 0; i < len; ++i) *str++ = tmp[i];
  while (len < size--) *str++ = L' ';

  return str;
}

static wchar_t *iaddr(wchar_t *str, unsigned wchar_t *addr, int size, int precision, int type)
{
  wchar_t tmp[24];
  int i, n, len;

  len = 0;
  for (i = 0; i < 4; i++)
  {
    if (i != 0) tmp[len++] = '.';
    n = addr[i];
    
    if (n == 0)
      tmp[len++] = digits[0];
    else
    {
      if (n >= 100) 
      {
        tmp[len++] = digits[n / 100];
        n = n % 100;
        tmp[len++] = digits[n / 10];
        n = n % 10;
      }
      else if (n >= 10) 
      {
        tmp[len++] = digits[n / 10];
        n = n % 10;
      }

      tmp[len++] = digits[n];
    }
  }

  if (!(type & LEFT)) while (len < size--) *str++ = L' ';
  for (i = 0; i < len; ++i) *str++ = tmp[i];
  while (len < size--) *str++ = L' ';

  return str;
}

#ifndef NOFLOAT

wchar_t *ecvtbuf(double arg, int ndigits, int *decpt, int *sign, wchar_t *buf);
wchar_t *fcvtbuf(double arg, int ndigits, int *decpt, int *sign, wchar_t *buf);

static void cfltcvt(double value, wchar_t *buffer, wchar_t fmt, int precision)
{
  int decpt, sign, exp, pos;
  wchar_t *digits = NULL;
  wchar_t cvtbuf[80];
  int capexp = 0;
  int magnitude;

  if (fmt == L'G' || fmt == L'E')
  {
    capexp = 1;
    fmt += L'a' - L'A';
  }

  if (fmt == L'g')
  {
    digits = ecvtbuf(value, precision, &decpt, &sign, cvtbuf);
    magnitude = decpt - 1;
    if (magnitude < -4  ||  magnitude > precision - 1)
    {
      fmt = L'e';
      precision -= 1;
    }
    else
    {
      fmt = L'f';
      precision -= decpt;
    }
  }

  if (fmt == L'e')
  {
    digits = ecvtbuf(value, precision + 1, &decpt, &sign, cvtbuf);

    if (sign) *buffer++ = '-';
    *buffer++ = *digits;
    if (precision > 0) *buffer++ = '.';
    memcpy(buffer, digits + 1, precision);
    buffer += precision;
    *buffer++ = capexp ? L'E' : L'e';

    if (decpt == 0)
    {
      if (value == 0.0)
        exp = 0;
      else
        exp = -1;
    }
    else
      exp = decpt - 1;

    if (exp < 0)
    {
      *buffer++ = '-';
      exp = -exp;
    }
    else
      *buffer++ = '+';

    buffer[2] = (exp % 10) + L'0';
    exp = exp / 10;
    buffer[1] = (exp % 10) + L'0';
    exp = exp / 10;
    buffer[0] = (exp % 10) + L'0';
    buffer += 3;
  }
  else if (fmt == L'f')
  {
    digits = fcvtbuf(value, precision, &decpt, &sign, cvtbuf);
    if (sign) *buffer++ = L'-';
    if (*digits)
    {
      if (decpt <= 0)
      {
        *buffer++ = L'0';
        *buffer++ = L'.';
        for (pos = 0; pos < -decpt; pos++) *buffer++ = L'0';
        while (*digits) *buffer++ = *digits++;
      }
      else
      {
        pos = 0;
        while (*digits)
        {
          if (pos++ == decpt) *buffer++ = '.';
          *buffer++ = *digits++;
        }
      }
    }
    else
    {
      *buffer++ = L'0';
      if (precision > 0)
      {
        *buffer++ = L'.';
        for (pos = 0; pos < precision; pos++) *buffer++ = L'0';
      }
    }
  }

  *buffer = L'\0';
}

static void forcdecpt(wchar_t *buffer)
{
  while (*buffer)
  {
    if (*buffer == L'.') return;
    if (*buffer == L'e' || *buffer == L'E') break;
    buffer++;
  }

  if (*buffer)
  {
    int n = wcslen(buffer);
    while (n > 0) 
    {
      buffer[n + 1] = buffer[n];
      n--;
    }

    *buffer = '.';
  }
  else
  {
    *buffer++ = '.';
    *buffer = '\0';
  }
}

static void cropzeros(wchar_t *buffer)
{
  wchar_t *stop;

  while (*buffer && *buffer != L'.') buffer++;
  if (*buffer++)
  {
    while (*buffer && *buffer != L'e' && *buffer != L'E') buffer++;
    stop = buffer--;
    while (*buffer == L'0') buffer--;
    if (*buffer == L'.') buffer--;
    while (*++buffer = *stop++);
  }
}

static wchar_t *flt(wchar_t *str, double num, int size, int precision, wchar_t fmt, int flags)
{
  wchar_t tmp[80];
  wchar_t c, sign;
  int n, i;

  // Left align means no zero padding
  if (flags & LEFT) flags &= ~ZEROPAD;

  // Determine padding and sign wchar_t
  c = (flags & ZEROPAD) ? L'0' : L' ';
  sign = 0;
  if (flags & SIGN)
  {
    if (num < 0.0)
    {
      sign = L'-';
      num = -num;
      size--;
    }
    else if (flags & PLUS)
    {
      sign = L'+';
      size--;
    }
    else if (flags & SPACE)
    {
      sign = L' ';
      size--;
    }
  }

  // Compute the precision value
  if (precision < 0)
    precision = 6; // Default precision: 6
  else if (precision == 0 && fmt == L'g')
    precision = 1; // ANSI specified

  // Convert floating point number to text
  cfltcvt(num, tmp, fmt, precision);

  // '#' and precision == 0 means force a decimal point
  if ((flags & SPECIAL) && precision == 0) forcdecpt(tmp);

  // 'g' format means crop zero unless '#' given
  if (fmt == L'g' && !(flags & SPECIAL)) cropzeros(tmp);

  n = wcslen(tmp);

  // Output number with alignment and padding
  size -= n;
  if (!(flags & (ZEROPAD | LEFT))) while (size-- > 0) *str++ = L' ';
  if (sign) *str++ = sign;
  if (!(flags & LEFT)) while (size-- > 0) *str++ = c;
  for (i = 0; i < n; i++) *str++ = tmp[i];
  while (size-- > 0) *str++ = L' ';

  return str;
}

#endif

int _vsnwprintf(wchar_t *buf, int cnt, const wchar_t *fmt, va_list args)
{
  int len;
  unsigned long num;
  int i, base;
  wchar_t *str;
  const wchar_t *s;

  int flags;            // Flags to number()

  int field_width;      // Width of output field
  int precision;        // Min. # of digits for integers; max number of chars for from string
  int qualifier;        // 'h', 'l', or 'L' for integer fields

  for (str = buf; *fmt; fmt++)
  {
    if (*fmt != L'%')
    {
      *str++ = *fmt;
      continue;
    }
                  
    // Process flags
    flags = 0;
repeat:
    fmt++; // This also skips first '%'
    switch (*fmt)
    {
      case L'-': flags |= LEFT; goto repeat;
      case L'+': flags |= PLUS; goto repeat;
      case L' ': flags |= SPACE; goto repeat;
      case L'#': flags |= SPECIAL; goto repeat;
      case L'0': flags |= ZEROPAD; goto repeat;
    }
          
    // Get field width
    field_width = -1;
    if (is_digit(*fmt))
      field_width = skip_atoi(&fmt);
    else if (*fmt == L'*')
    {
      fmt++;
      field_width = va_arg(args, int);
      if (field_width < 0)
      {
        field_width = -field_width;
        flags |= LEFT;
      }
    }

    // Get the precision
    precision = -1;
    if (*fmt == L'.')
    {
      ++fmt;    
      if (is_digit(*fmt))
        precision = skip_atoi(&fmt);
      else if (*fmt == L'*')
      {
        ++fmt;
        precision = va_arg(args, int);
      }
      if (precision < 0) precision = 0;
    }

    // Get the conversion qualifier
    qualifier = -1;
    if (*fmt == L'h' || *fmt == L'l' || *fmt == L'L')
    {
      qualifier = *fmt;
      fmt++;
    }

    // Default base
    base = 10;

    switch (*fmt)
    {
      case L'c':
        if (!(flags & LEFT)) while (--field_width > 0) *str++ = L' ';
        *str++ = (unsigned wchar_t) va_arg(args, int);
        while (--field_width > 0) *str++ = L' ';
        continue;

      case L's':
        s = va_arg(args, wchar_t *);
        if (!s) s = L"<NULL>";
        len = strnlen(s, precision);
        if (!(flags & LEFT)) while (len < field_width--) *str++ = L' ';
        for (i = 0; i < len; ++i) *str++ = *s++;
        while (len < field_width--) *str++ = L' ';
        continue;

      case L'p':
        if (field_width == -1)
        {
          field_width = 2 * sizeof(void *);
          flags |= ZEROPAD;
        }
        str = number(str, (unsigned long) va_arg(args, void *), 16, field_width, precision, flags);
        continue;

      case L'n':
        if (qualifier == L'l')
        {
          long *ip = va_arg(args, long *);
          *ip = (str - buf);
        }
        else
        {
          int *ip = va_arg(args, int *);
          *ip = (str - buf);
        }
        continue;

      case L'A':
        flags |= LARGE;

      case L'a':
        if (qualifier == L'l')
          str = eaddr(str, va_arg(args, unsigned wchar_t *), field_width, precision, flags);
        else
          str = iaddr(str, va_arg(args, unsigned wchar_t *), field_width, precision, flags);
        continue;

      // Integer number formats - set up the flags and "break"
      case L'o':
        base = 8;
        break;

      case L'X':
        flags |= LARGE;

      case L'x':
        base = 16;
        break;

      case L'd':
      case L'i':
        flags |= SIGN;

      case L'u':
        break;

#ifndef NOFLOAT

      case L'E':
      case L'G':
      case L'e':
      case L'f':
      case L'g':
        str = flt(str, va_arg(args, double), field_width, precision, *fmt, flags | SIGN);
        continue;

#endif

      default:
        if (*fmt != L'%') *str++ = L'%';
        if (*fmt)
          *str++ = *fmt;
        else
          --fmt;
        continue;
    }

    if (qualifier == L'l')
      num = va_arg(args, unsigned long);
    else if (qualifier == L'h')
    {
      if (flags & SIGN)
        num = va_arg(args, int);
      else
        num = va_arg(args, unsigned int);
    }
    else if (flags & SIGN)
      num = va_arg(args, int);
    else
      num = va_arg(args, unsigned int);

    str = number(str, num, base, field_width, precision, flags);
  }

  *str = L'\0';
  return str - buf;
}

int swprintf(wchar_t *buf, size_t cnt, const wchar_t *fmt, ...)
{
  va_list args;
  int n;

  va_start(args, fmt);
  n = _vsnwprintf(buf,cnt,fmt,args);
  va_end(args);
  
  return n;
}

size_t wcslen(const wchar_t *src)
{
	int res = 0;
	while(*src++)
		res++;

	return res;
}

int iswspace(wint_t c)
{
      return ((c >= 0x0009 && c <= 0x000d) || c == 0x0020 || c == 0x1680 ||
              (c >= 0x2000 && c <= 0x2006) ||
              (c >= 0x2008 && c <= 0x200b) ||
              c == 0x2028 || c == 0x2029 ||
              c == 0x205f || c == 0x3000);
}

int iswdigit(wint_t c)
{
	int digit = c - L'0';
	return digit >= 0 && digit <= 9;
}

/* EOF */
